home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-12-14 | 24.5 KB | 674 lines | [TEXT/R*ch] |
- This is an excerpt of an article from MacTech Magazine, January 1996.
- Copyright 1996, Xplain Corp.
-
- Let’s take a look at some code, starting with the main() function:
-
- main(int argc, char* argv[])
- {
- // to randomize the movement of the bouncing ball
- srand((long) sys_time());
-
- // make the new application object and start it running
- my_app = new TBounceApp();
- my_app->Run();
-
- // application is finished so cleanup and return
- delete my_app;
- return 0;
- }
-
- The basic idea is to create the application object and start it running. The
- Run() function returns when the application quits. The TBounceApp object is then
- deleted and the program returns. Most applications on the BeBox will have a
- similar main() function.
- The application object
- The BApplication object is the real launching point for most applications, and
- BeBounce is no exception. Here is the definition of the TBounceApp class:
-
-
-
- class TBounceApp : public BApplication {
- public:
- TBounceApp();
- virtual ~TBounceApp();
-
- virtual void MessageReceived(BMessage *msg);
- void InitPartner(thread_id tid);
- void RemovePartner();
- bool SendToPartner(BMessage *msg);
- bool SendPosition(BRect rect);
-
- private:
- TWindow *fWindow;
- BMessenger *fPartner;
- };
-
- The application constructor does several interesting things. First, when the
- application is launched it needs to determine how many instances of the BeBounce
- application are already running. Here is a portion of that function:
-
- TBounceApp::TBounceApp()
- : BApplication(MY_SIGNATURE)
- {
- ... // some initialization code
-
- /*
- This version of BeBounce only supports 2 instances of the application
- running at
- the same time. So if there are already 2 instances running force a QUIT. */
- BList list;
- be_roster->GetThreadList(MY_SIGNATURE, &list);
- long app_count = list.CountItems();
- if (app_count > 2) {
- // only support 2 applications at a time
- PostMessage(QUIT_REQUESTED);
- return;
- }
-
- ... // more initialization to be discussed below
- }
-
- The above code uses the be_roster (aka “rooster”) object, a system-wide object
- that, among other things, maintains information on all running applications. The
- roster returns a list of all running applications that have the signature
- MY_SIGNATURE. If there are more than two instances of the application already
- running, this instance simply quits.
- If this is the first instance of the application it simply creates the window:
-
- if (app_count == 1) {
- // The first instance of BeBounce will have a ball.
- fWindow = new TWindow(wpos, "Bouncing 1", TRUE);
- } else {
- ... // the second instance of BeBounce
- }
-
- If there are two instances, things are trickier:
-
- if (app_count == 1) {
- ... // the first instance of BeBounce
- } else {
- // This is the second instance of the BeBounce app
- fWindow = new TWindow(wpos, "Bouncing 2", FALSE);
-
- ... // determine which of the 2 instances is myself and which is the
- partner.
-
- // tid is the “thread id” of our partner!
- InitPartner(tid);
-
-
-
-
- /* Send the introductory message along with my thread
- id and location in screen coordinates. */
- BMessage *msg = new BMessage(BB_HELLO);
- msg->AddLong("thread", Thread());
- msg->AddRect("rect", wpos);
- SendToPartner(msg);
- }
-
- Here, as before, the application creates the window. It then determines the
- thread id of its partner application and initializes a BMessenger object for
- communicating with the partner. The code that creates the messenger is in the
- utility member function TBounceApp::InitPartner():
-
- void TBounceApp::InitPartner(thread_id tid)
- {
- if (fPartner) // BB_HELLO race condition
- return;
-
- // establish a ‘messenger’ as the link to our partner
- fPartner = new BMessenger(MY_SIG, tid);
- if (fPartner->Error() != NO_ERROR) {
- delete fPartner;
- fPartner = NULL;
- }
- }
-
- It is important to note that this function handles the race condition surrounding
- the BB_HELLO message. Imagine launching two instances of BeBounce at the same
- time. The call to GetThreadList() could return two instances to each
- application. In this case both applications will behave as the second instance
- and both will send BB_HELLO messages. This will result in
- TBounceApp::InitPartner being called twice by each application. In this
- particular application the only result would be a small memory leak of the first
- BMessenger object. Understanding these types of race conditions is one of the
- critical aspects of designing software for the BeBox. Note that the code does
- not handle three applications launching simultaneously. In this case all three
- applications might decide that they are the “third wheel” and quit.
- After creating the messenger, the code constructs a BB_HELLO BMessage object.
- The next step is to add the thread id of this application to the message. The
- other application uses this data to construct its own BMessenger. The window
- position is also added to the message so that the partner knows this window’s
- initial position. The message is sent using the utility function TBounceApp::
- SendToPartner. Other than error handling SendToPartner contains just one line:
-
- bool TBounceApp::SendToPartner(BMessage *msg)
- {
- ... // error handling
- fPartner->SendMessage(msg);
- ... // error handling
- }
- The final steps of the constructor are to create a “Stop Ball” menu item and show
- the window on screen:
-
- // inside the TBounceApp constructor
- /*
- A little bit of menu code. Add a ‘Stop Ball’ menu item to the application
- menu
- (found in the Dock window) By default items in the “app” menu post messages
- to
- the application. In this case the message should be “targeted” to the
- window, not
- the app.
- */
- BPopUpMenu *menu = AppMenu();
- BMenuItem *item = new BMenuItem("Stop Ball",
- new BMessage(BB_STOP), 'K');
- item->SetTarget(fWindow);
- menu->AddItem(item);
-
- fWindow->Show();
- // this is the end of the TBounceApp constructor
-
- The protocol that the applications use to communicate with each other is most
- apparent in the TBounceApp:: MessageReceived() member function:
-
- void TBounceApp::MessageReceived(BMessage *msg)
- {
- switch (msg->what) {
- case BB_HELLO:
- ... // a second BeBounce application is saying hello!
- break;
- case BB_GOODBYE:
- ... // our partner is quitting
- break;
- case BB_WINDOW_MOVED:
- ... // partner moved, and has given us its new position
- break;
- case BB_BALL:
- ... // we’ve just been given the ball
- break;
- }
- }
-
- Receiving a BB_HELLO message means that a second instance of the application is
- introducing itself. Here is the code for handling this message:
-
- case BB_HELLO:
- if (fWindow->Lock()) {
- /*
- A new instance of BeBounce was just launched
- and sent us the introductory message.
- */
- InitPartner(msg->FindLong("thread"));
-
- // Initialize our partner’s current location.
- pos = msg->FindRect("rect");
- fWindow->PartnerMoved(pos);
-
- // Tell our new partner our current location.
- pos = fWindow->Frame();
- SendPosition(pos);
- fWindow->Unlock();
- }
- break;
-
- This code initializes a BMessenger using TBounceApp::InitPartner() and places the
- thread id in the message. The partner’s window position is also retrieved from
- the message and saved. Finally the code determines the position of its window
- and sends that to the partner. The communication link between the two
- applications is now open.
-
- The next message is BB_GOODBYE. This message is sent as BeBounce applications
- quit. The code for responding to this message is quite simple:
-
- case BB_GOODBYE:
- if (fWindow->Lock()) {
- // Our partner is quitting.
- RemovePartner();
- if (msg->HasBool("ball"))
- fWindow->AddBall();
- fWindow->Unlock();
- }
- break;
-
- The partner is removed using the function TBounceApp::RemovePartner, the
- complement of the InitPartner function seen earlier. In addition, if the
- quitting partner currently had the ball a new ball is added to this application’s
- window. This ensures that there is always a bouncing ball. The code that sends
- the BB_GOODBYE message is described in the section on the window.
- The final two messages sent between the applications, BB_WINDOW_MOVED and
- BB_BALL, are status messages informing the partner that either the window moved
- or the ball has moved through the gap. Here is the code, without further
- explanation:
-
- case BB_WINDOW_MOVED:
- /*
- Our partner is informing us that it moved. This message is
- continually
- generated as the other window is being moved. TWindow::PartnerMoved
- redraws the window to reflect the new position.
- */
- if (fWindow->Lock()) {
- pos = msg->FindRect("rect");
- fWindow->PartnerMoved(pos);
- fWindow->Unlock();
- }
- break;
-
- case BB_BALL:
- // Our partner just passed us the ball.
- if (fWindow->Lock()) {
- BPoint speed = msg->FindPoint("speed");
- float rel_loc = msg->FindFloat("rel_loc");
- fWindow->AddBall(rel_loc, speed);
- fWindow->Unlock();
- }
- break;
- The window object
- The next class of interest is TWindow, a subclass of BWindow. In the BeBounce
- application the window is responsible for managing the ball and for informing the
- partner application of particular events (see description of
- TBounceApp::MessageReceived() above). The window also presents the UI for this
- application so there is some description of how applications can create and
- manage UI on the BeBox. In the Be operating system a BWindow object provides an
- area that can display and retain rendered images. A BWindow by itself cannot
- draw, only BViews can draw. However, a BView must belong to a window in order to
- draw. These two classes work hand in hand.
- Here is a portion of the TWindow class definition:
-
- class TWindow : public BWindow {
- public:
- TWindow(BRect frame, const char *title,
- bool with_ball);
- virtual ~TWindow();
-
- virtual void MessageReceived(BMessage *msg);
- virtual bool QuitRequested();
- virtual void FrameMoved(BPoint new_position);
- void AddBall();
- void AddBall(float rel_location, BPoint speed);
- ... // a few more member functions
-
- private:
- void DrawOffScreen(BView *v);
- ... // a couple more private member functions and then
- // some private data members
- };
- The TWindow constructor contains several code fragments worth discussing:
-
- TWindow::TWindow(BRect frame, const char *title, bool ball)
- : BWindow(frame, title, TITLED_WINDOW, NOT_RESIZABLE)
- {
- ... // some initialization removed
-
- if (ball)
- AddBall();
-
- /*
- The drawing takes place in the view fOffView that was added to the offscreen
-
- bitmap fBitmap. In this way we’ll do the drawing offscreen and then just
- blit the
- result to the screen.
- */
-
- fBitmap = new BBitmap(b, COLOR_8_BIT, TRUE);
- fOffView = new BView(b, "", 0, WILL_DRAW);
-
- fBitmap->Lock();
- fBitmap->AddChild(fOffView);
- DrawOffScreen(fOffView); // draw the initial contents offscreen
- fBitmap->Unlock();
-
- /*
- This view is added to the visible window. Its only role is to blit the
- offscreen
- bitmap to the visible window.
- */
- fMainView = new TBitmapView(b, fBitmap);
- AddChild(fMainView);
-
- ... // some initialization removed
- }
-
- Here is the first look at how windows and views are created and used on the
- BeBox. To get smooth animation TWindow uses an offscreen bitmap (a BBitmap
- object) and a basic BView object (fOffView) for the rendering. It is in the
- context of this view that all the actual drawing occurs. The graphics
- primitives, like BView::FillRect() and BView::FillArc() that create the effect of
- a ball bouncing off real walls, takes place in this offscreen view. The last
- view created, an instance of the TBitmapView class, is simply the helper view
- that blits the bits from the offscreen bitmap into the onscreen window. Since
- the TBitmapView class (a subclass of BView) is so simple here is all its code:
-
- TBitmapView::TBitmapView(BRect frame, BBitmap *bitmap)
- : BView(frame, "", FOLLOW_NONE, WILL_DRAW)
- {
- /*
- The only job of this view is to blit the offscreen bits into the onscreen
- view.
- */
- fBitmap = bitmap;
- }
-
- void TBitmapView::Draw(BRect update)
- {
- // blit the bitmap with source and dest rectangle ‘update’
- DrawBitmap(fBitmap, update, update);
- }
-
- As stated previously, a BWindow is a kind of BLooper, which in turn is a kind of
- BReceiver. As such, a window runs in its own message loop and it can receive
- messages. A BView is also a kind of BReceiver so it too can receive messages. A
- message posted to a window (using PostMessage) can be “targeted” to either the
- window or a view contained within that window. Because of the connection between
- windows and views, a view typically receives messages in the context of its
- window. Said another way, the handling of messages targeted to a view occurs in
- the window’s thread.
- On the BeBox, user actions on the keyboard and mouse are turned into BMessages,
- called interface events. However, these messages are not handled by
- MessageReceived(), like the BeBounce message BB_HELLO. Instead, interface events
- are dispatched to a set of virtual functions corresponding to the action. Here
- are a few of those functions:
-
- BView::MouseDown() // mouse down event in that view
- BView::KeyDown() // keydown event while that view was
- // the “focused” view
- BView::FrameResized // the view changed size
- BWindow::FrameMoved() // the window position moved
- BWindow::QuitRequested() // click on close-box of window
-
- It turns out that in BeBounce only two interface events are of interest, the
- FrameMoved and QuitRequested events. The system repeatedly generates the
- FrameMoved event as a window is being dragged. This event is of interest because
- as the window moves, so should the gap that exists between the partner windows.
- Here is the code for responding to the FrameMoved event:
-
- void TWindow::FrameMoved(BPoint new_pos)
- {
- /*
- As window is moved around the screen we inform our
- partner of our new location. Also update our gap.
- */
- fMyFrame = Frame();
- if (my_app->SendPosition(fMyFrame))
- WindowsMoved(fMyFrame, fPartnerFrame);
- }
-
- This code gets the window’s current location and sends it to our partner so that
- our partner can update its gap. TBounceApp::SendPosition() sends the
- BB_WINDOW_MOVED message to the partner (see the code in
- TBounceApp::MessageReceived() for the code that handles this message). Only if
- we have a partner will TBounceApp::SendPosition() return TRUE. In this case the
- code calls the TWindow::WindowsMoved() function, updating this window’s gap. As
- BeBounce windows move about the screen everything is kept in synch.
- The other event of interest is generated when the user clicks on the close box of
- the window. In BeBounce closing the window should also cause the application to
- quit. This means that the window has to listen for the QuitRequested event:
-
- bool TWindow::QuitRequested()
- {
- /*
- The window was asked to quit/close. Send a message to our partner, giving
- him
- the ball if we’ve currently got it.
- */
- BMessage *m = new BMessage(BB_GOODBYE);
- if (fBall) {
- fBall->Quit();
- fBall = NULL;
- m->AddBool("ball", TRUE);
- }
- my_app->SendToPartner(m);
-
- // Tell the app to go ahead and quit.
- my_app->PostMessage(QUIT_REQUESTED);
- return TRUE;
- }
-
- The first thing to do when the QUIT_REQUESTED message is received is to tell our
- partner good-bye, passing the ball along if it is in our possession. It would
- not be polite to quit with ball in hand. Please note that the TBounceApp::
- SendToPartner() does the correct thing if there is no partner. Next the window
- posts a message to the application telling it to quit as well. The return value
- of TRUE causes to window to immediately quit.
- The ball
- The last object to describe is the ball. Several factors influenced the design
- of the ball. It is desirable to keep the ball independent from the window, yet
- they still need to communicate with each other. Also, the ball periodically
- requires time to simulate motion. One design that accommodates these guidelines
- is to construct a subclass of BLooper called TBall. This means that the ball has
- its own thread and it is able to receive messages. Here is part of the class
- definition:
-
- class TBall : public BLooper {
- public:
- TBall(TWindow *window, BRect bounds,
- BPoint center, float
- radius,
- BPoint speed);
-
- void Draw(BView *view);
- virtual void MessageReceived(BMessage *msg);
-
- void Lock();
- void Unlock();
- void SetGap(float start, float end);
- void SetEnabled(bool state);
- ... // a couple other member functions
-
- private:
- void NextPosition(bool *hit, float *angle);
- void Tick();
-
- BLocker fLock;
- TWindow *fWindow;
- ... // a bunch of data members for “state” like size,
- // speed/direction, postion, etc
- };
-
- Having a thread per ball would not scale to an application that had 1000 bouncing
- balls, but for demonstration purposes it works well.
- Most of the constructor for the TBall class is fairly simple, initializing the
- various data members. The most interesting aspect of the constructor is that it
- starts the looper thread running and posts the first BB_TICK message to get the
- animation working:
-
- TBall::TBall(TWindow *window, BRect bounds, BPoint center,
- float radius, BPoint speed)
- : fLock()
- {
- ... // initialize parameters like size, location, and speed
-
- /*
- Get the looper thread rolling. Unlike the call to BApplication::Run, which
- does not
- return, this call does return.
- */
- Run();
-
- // post initial message to get animation going
- PostMessage(BB_TICK);
- }
-
- The TBall’s MessageReceived function handles the BB_TICK message:
-
- void TBall::MessageReceived(BMessage *msg)
- {
- switch (msg->what) {
- case BB_TICK:
- Lock();
- Tick();
- Unlock();
-
- ... // sleep for a little bit of time
-
- // post next message to continue animation
- PostMessage(BB_TICK);
- break;
- }
- }
-
- The Lock() and Unlock() calls shown above deserve further explanation. The
- BeBounce application was designed so that the window “communicates” with the
- ball, not by posting messages to it, but by calling specific member functions;
- see the definition of the TBall class. For example, one of those functions is
- TBall::SetGap():
-
- void TBall::SetGap(float start, float end)
- {
- Lock();
- fGapStart = start;
- fGapEnd = end;
- Unlock();
- }
-
- A consequence of this design is that two separate threads access the same data
- structure, so some form of synchronization is required. The BLocker (also known
- as “blocker”) class provides this synchronization. Imagine if the locking was
- not present. Then in the middle of the looper thread’s calculation to determine
- if the ball hit that gap the window thread could come along and change the gap.
- This would lead to undefined behavior. Code in TBounceApp:: MessageReceived()
- locks the window for this same reason.
- An alternate design would have the window sending messages to the ball. In this
- case, locking would not be an issue. The act of posting messages implicitly
- provides the synchronization needed. The ball’s looper thread can only handle
- one message at a time. In this example the trade-off might be the latency of
- messages changing the “gap” position or starting and stopping the ball, with the
- Stop Ball menu item. The preferred method should be determined on a case by case
- basis. For educational purposes the BeBounce application uses both styles of
- programming.
- Back to the BB_TICK message. Most of the work in animating the ball is done in
- TBall::Tick(), and most of that code is the math and geometry needed to animate
- the bouncing ball and determine when the ball either hits a wall or flies through
- the gap. As little of this code is specific to the BeBox we will not go into any
- more detail except to show how the ball draws.
-
- void TBall::Tick()
- {
- ... // move ball to new position
-
- // inform the window to redraw the window.
- BMessage *msg = new BMessage(BB_DRAW);
- msg->AddRect("update", updateRect);
- fWindow->PostMessage(msg);
-
- if (hit_hole) {
- /*
- The ‘gap’ was hit. So we package up the info like speed and relative
- location,
- which gives our partner enough information to have the ball appear in
- the
- correct place.
- */
- BMessage *msg = new BMessage(BB_HIT_HOLE);
- ... // adding info to message
- fWindow->PostMessage(msg);
- }
- }
-
- The TBall::Tick() function moves the ball to the next position and then posts a
- message to the window telling it to redraw itself. Additionally, if the ball
- hits the “gap” a BB_HIT_HOLE message is posted to the window:
- When the window receives the BB_DRAW message (in its MessageReceived function) it
- asks the ball to draw itself in its current location. The drawing is done by
- TBall::Draw():
-
- void TBall::Draw(BView *view)
- {
- // The balls draws itself in the given view
- Lock();
- rgb_color c = view->FrontColor();
-
- view->SetPenSize(1.0);
- view->SetFrontColor(150, 30, 30);
- view->FillArc(fCenter, fRadius, fRadius, 0.0, 360.0);
- view->SetFrontColor(c);
- Unlock();
- }
-
- The view passed to TBall::Draw() is the same view that was added to the offscreen
- bitmap in the TWindow’s constructor, so the ball is being drawn offscreen. The
- window then gets the TBitmapView (described earlier) to blit the offscreen image
- onto the screen.
- The code for handling the BB_HIT_HOLE message is in TWindow::MessageReceived:
-
- void TWindow::MessageReceived(BMessage *msg)
- {
- switch (msg->what) {
- case BB_HIT_HOLE:
- /*
- The ball is telling us that it just hit the
- hole. So it should get sent to the partner.
- */
- BMessage *m = new BMessage(BB_BALL);
- fBall->Lock();
- m->AddPoint("speed", msg->FindPoint("speed"));
- m->AddFloat("rel_loc", msg->FindFloat("rel_loc"));
- fBall->Unlock();
-
- // send the ‘ball’ to our partner
- my_app->SendToPartner(m);
-
- // delete the ball from this window
- fBall->Quit();
- fBall = NULL;
-
- // redraw the window
- Update();
- break;
-
- ... // handling of other messages
- }
- }
-
- The window responds to the BB_HIT_HOLE message by sending the BB_BALL message to
- the partner application giving it the ball. It puts the necessary information
- into the message so that the partner can create the new ball in the correct
- position, with the correct speed and direction. Finally, the window deletes the
- ball.
- That is how the BeBounce application works. Obvious improvements to BeBounce
- would be to support more than two applications, multiple windows within the same
- application, and multiple balls. Another useful addition would be a separate
- control window with some controls for things like ball speed. All of these
- enhancements are feasible given the design of the Be operating system. To
- support an arbitrary number of applications each application would maintain a
- list of partners, having a BMessenger object to each one. Multiple balls in a
- window could either communicate with one another (for hit testing purposes)
- directly or use the window object as the arbitrator.
- Hopefully this sample application has provided a taste of developing an
- application for the BeBox. The next question is: How do Be applications get
- built?
- Development Tools
- You write applications for the Be environment using tools supplied by one of the
- leading tool suppliers in the industry – Metrowerks. Currently the preferred
- development environment is a Macintosh with the latest version of CodeWarrior and
- Be-supplied headers and libraries. Using CodeWarrior on the Macintosh, a
- developer can write, compile, and link a Be application. Of course, you have to
- transfer the application to the BeBox before it will run. Since the BeBox
- supports ftp and includes a command-line shell (based on bash) the process of
- quitting your app, bringing over a new one from the Macintosh, and running it on
- the BeBox can be completely automated. There is also support for using the
- source-level debugger on the Macintosh to debug an application while it is
- running on the BeBox. Metrowerks is currently working on a port of the full
- CodeWarrior Integrated Development Environment to the Be operating system. This
- native version of CodeWarrior, along with full technical developer documentation,
- will be shipped with every BeBox.
- Conclusion
- This is just a small taste of the BeBox. We have presented an overview of the Be
- operating system along with some sample code to give you the general feel of Be
- application development. To learn more about the Be operating system, please
- visit our web site, where we provide on-line versions of our developer
- documentation as well as information about our product and company. We encourage
- developers interested in joining our support program to mail in the developer
- form, which you can find on the web site.
- Be has many plans for the future. We are continuing to improve and add features
- to the Be operating system and development tools. We are also working on future
- versions of the BeBox, which will include more and faster processors – including
- a machine with four PowerPC 604 chips for the horsepower hungry.
- We hope this article has succeeded in whetting your appetite for Be programming.
- We look forward to seeing all the incredible applications we know developers will
- write for the BeBox.
-